 /*******************************************************************************
  * Copyright (c) 2004, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.internal.intro.impl.parts;

 import java.util.Enumeration ;
 import java.util.Hashtable ;

 import org.eclipse.osgi.util.NLS;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.custom.StackLayout;
 import org.eclipse.swt.graphics.Point;
 import org.eclipse.swt.graphics.Rectangle;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Layout;
 import org.eclipse.ui.IMemento;
 import org.eclipse.ui.forms.events.HyperlinkAdapter;
 import org.eclipse.ui.forms.events.HyperlinkEvent;
 import org.eclipse.ui.forms.widgets.FormToolkit;
 import org.eclipse.ui.forms.widgets.ImageHyperlink;
 import org.eclipse.ui.internal.intro.impl.IIntroConstants;
 import org.eclipse.ui.internal.intro.impl.IntroPlugin;
 import org.eclipse.ui.internal.intro.impl.Messages;
 import org.eclipse.ui.internal.intro.impl.model.AbstractIntroPage;
 import org.eclipse.ui.internal.intro.impl.model.IntroModelRoot;
 import org.eclipse.ui.internal.intro.impl.model.IntroStandbyContentPart;
 import org.eclipse.ui.internal.intro.impl.model.loader.ExtensionPointManager;
 import org.eclipse.ui.internal.intro.impl.model.loader.ModelLoaderUtil;
 import org.eclipse.ui.internal.intro.impl.util.ImageUtil;
 import org.eclipse.ui.internal.intro.impl.util.Log;
 import org.eclipse.ui.internal.intro.impl.util.StringUtil;
 import org.eclipse.ui.intro.IIntroPart;
 import org.eclipse.ui.intro.config.CustomizableIntroPart;
 import org.eclipse.ui.intro.config.IStandbyContentPart;

 /**
  * Standby part is responsible for managing and creating IStandbycontent parts.
  * It knows how to create and cache content parts. It also handles saving and
  * restoring its own state. It does that by caching the id of the last content
  * part viewed and recreating that part on startup. It also manages the life
  * cycle of content parts by creating and initializing them at the right
  * moments. It also passes the momento at appropriate times to these content
  * parts to enable storing and retrieving of state by content parts. Content
  * parts are responsible for recreating there own state, including input, from
  * the passed momemnto. When the Return to Introduction link is clicked, the
  * Intro goes out of standby content mode, and the standby content parts are not
  * shown anymore until the user explicitly asks for a part again. This is
  * accomplished through a data flag on the CustomizableIntroPart control.
  *
  */
 public class StandbyPart implements IIntroConstants {

     private FormToolkit toolkit;
     private IntroModelRoot model;
     protected ImageHyperlink returnLink;
     protected Control separator;
     private Composite container;
     protected Composite content;
     private IIntroPart introPart;
     private EmptyStandbyContentPart emptyPart;
     private IMemento memento;

     // hastable has partIds as keys, and ControlKeys are values.
 private Hashtable cachedContentParts = new Hashtable ();

     private ControlKey cachedControlKey;

     class StandbyLayout extends Layout {

         private int VGAP = 9;
         private int VMARGIN = 5;
         private int HMARGIN = 5;
         private int SEPARATOR_HEIGHT = 1;

         /*
          * (non-Javadoc)
          *
          * @see org.eclipse.swt.widgets.Layout#computeSize(org.eclipse.swt.widgets.Composite,
          * int, int, boolean)
          */
         protected Point computeSize(Composite composite, int wHint, int hHint,
                 boolean flushCache) {
             Point lsize = returnLink.computeSize(SWT.DEFAULT, SWT.DEFAULT,
                 flushCache);
             Point csize = content.computeSize(SWT.DEFAULT, SWT.DEFAULT,
                 flushCache);
             int width = Math.max(lsize.x + 2 * HMARGIN, csize.x);
             int height = VMARGIN + lsize.y + VGAP + SEPARATOR_HEIGHT + csize.y;
             return new Point(width, height);
         }

         /*
          * (non-Javadoc)
          *
          * @see org.eclipse.swt.widgets.Layout#layout(org.eclipse.swt.widgets.Composite,
          * boolean)
          */
         protected void layout(Composite composite, boolean flushCache) {
             Rectangle carea = composite.getClientArea();
             int lwidth = carea.width - HMARGIN * 2;
             Point lsize = returnLink.computeSize(lwidth, SWT.DEFAULT,
                 flushCache);
             int x = HMARGIN;
             int y = VMARGIN;
             returnLink.setBounds(x, y, lsize.x, lsize.y);
             x = 0;
             y += lsize.y + VGAP;
             separator.setBounds(x, y, carea.width, SEPARATOR_HEIGHT);
             y += SEPARATOR_HEIGHT;
             content.setBounds(x, y, carea.width, carea.height - VMARGIN
                     - lsize.y - VGAP - SEPARATOR_HEIGHT);
         }
     }

     /**
      * @param parent
      */
     public StandbyPart(IntroModelRoot model) {
         this.model = model;
     }


     public void init(IIntroPart introPart, IMemento memento) {
         this.introPart = introPart;
         this.memento = memento;
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.ui.intro.IIntroPart#saveState(org.eclipse.ui.IMemento)
      */
     private IMemento getMemento(IMemento memento, String key) {
         if (memento == null)
             return null;
         return memento.getChild(key);
     }

     public void createPartControl(Composite parent) {
         toolkit = new FormToolkit(parent.getDisplay());
         // parent container. Has custom layout. Has return link and content
 // stack composite.
 container = toolkit.createComposite(parent);
         container.setLayout(new StandbyLayout());

         // return hyper link.
 ImageUtil.registerImage(ImageUtil.BACK, "full/elcl16/home_nav.gif"); //$NON-NLS-1$
 returnLink = toolkit.createImageHyperlink(container, SWT.WRAP
                 | SWT.CENTER);
         returnLink.setImage(ImageUtil.getImage(ImageUtil.BACK));
         returnLink.addHyperlinkListener(new HyperlinkAdapter() {

             public void linkActivated(HyperlinkEvent e) {
                 doReturn();
             }
         });

         // create horizontal separator
 separator = toolkit.createCompositeSeparator(container);
         // content stack container
 content = toolkit.createComposite(container);
         StackLayout slayout = new StackLayout();
         slayout.marginWidth = slayout.marginHeight = 0;
         content.setLayout(slayout);

         boolean success = false;
         if (memento != null) {
             success = restoreState(memento);
             if (!success)
                 // add empty standby content.
 addAndShowEmptyPart(Messages.StandbyPart_canNotRestore);
         }

         updateReturnLinkLabel();
     }

     /**
      * Empty content part used as backup for failures.
      *
      */
     private void addAndShowEmptyPart(String message) {
         if (emptyPart == null)
             emptyPart = new EmptyStandbyContentPart();
         addStandbyContentPart(EMPTY_STANDBY_CONTENT_PART, emptyPart);
         emptyPart.setMessage(message);
         setTopControl(EMPTY_STANDBY_CONTENT_PART);
     }

     /**
      * Tries to create the last content part viewed, based on content part id..
      *
      * @param memento
      * @return
      */
     private boolean restoreState(IMemento memento) {
         String contentPartId = memento
             .getString(MEMENTO_STANDBY_CONTENT_PART_ID_ATT);
         if (contentPartId == null)
             return false;
         // create the cached content part. Content parts are responsible for
 // storing and reading their input state.
 return showContentPart(contentPartId, null);
     }


     /**
      * Sets the into part to standby, and shows the passed standby part, with
      * the given input.
      *
      * @param partId
      * @param input
      */
     public boolean showContentPart(String partId, String input) {
         // Get the IntroStandbyContentPart that maps to the given partId.
 IntroStandbyContentPart standbyPartContent = ExtensionPointManager
             .getInst().getSharedConfigExtensionsManager()
             .getStandbyPart(partId);

         if (standbyPartContent != null) {
             String standbyContentClassName = standbyPartContent.getClassName();
             String pluginId = standbyPartContent.getPluginId();

             Object standbyContentObject = ModelLoaderUtil.createClassInstance(
                 pluginId, standbyContentClassName);
             if (standbyContentObject instanceof IStandbyContentPart) {
                 IStandbyContentPart contentPart = (IStandbyContentPart) standbyContentObject;
                 Control c = addStandbyContentPart(partId, contentPart);
                 if (c != null) {
                     try {
                         setTopControl(partId);
                         setInput(input);
                         return true;
                     } catch (Exception e) {
                         Log.error("Failed to set the input: " + input //$NON-NLS-1$
 + " on standby part: " + partId, e); //$NON-NLS-1$
 }
                 }

                 // failed to create the standby part, show empty part and signal
 // failure.
 String message = NLS.bind(Messages.StandbyPart_failedToCreate,
                     partId);
                 addAndShowEmptyPart(message);
                 return false;

             }
         }

         // no content part defined with the passed partId, show empty part and
 // signal failure.
 String message = NLS.bind(Messages.StandbyPart_nonDefined, partId);
         addAndShowEmptyPart(message);
         return false;
     }

     /**
      * Creates a standbyContent part in the stack only if one is not already
      * created. The partId is used as tke key in the cache. The value is an
      * instance of ControlKey that wraps a control/StandbyPart pair along with
      * the corresponding part id. This is needed to retrive the control of a
      * given standby part. The IMemento should be passed to the StandbyPart when
      * it is initialized.
      *
      * @param standbyContent
      */
     public Control addStandbyContentPart(String partId,
             IStandbyContentPart standbyContent) {

         ControlKey controlKey = getCachedContent(partId);
         if (controlKey == null) {

             try {
                 standbyContent.init(introPart, getMemento(memento,
                     MEMENTO_STANDBY_CONTENT_PART_TAG));
                 standbyContent.createPartControl(content, toolkit);
             } catch (Exception e) {
                 // a standby content part throws a PartInitException, log fact.
 Log.error(
                     "Failed to create part for standby part: " + partId, e); //$NON-NLS-1$
 return null;
             }

             Control control = standbyContent.getControl();
             controlKey = new ControlKey(control, standbyContent, partId);
             cachedContentParts.put(partId, controlKey);
             if (partId.equals(EMPTY_STANDBY_CONTENT_PART))
                 // just in case it was created explicity, reuse it.
 emptyPart = (EmptyStandbyContentPart) standbyContent;

             if (controlKey.getControl() == null) {
                 // control is null. This means that interface was not
 // implemented properly. log fact.
 String message = StringUtil
                     .concat("Standby Content part: ", partId, //$NON-NLS-1$
 " has a null Control defined. This prevents the part from being displayed.") //$NON-NLS-1$
 .toString();
                 Log.error(message, null);
                 return null;
             }
         }

         return controlKey.getControl();
     }



     public void setInput(Object input) {
         IStandbyContentPart standbyContent = cachedControlKey.getContentPart();
         standbyContent.setInput(input);
         updateReturnLinkLabel();
         container.layout();
     }


     public void setTopControl(String key) {
         cachedControlKey = getCachedContent(key);
         if (cachedControlKey != null) {
             setTopControl(cachedControlKey.getControl());
         }
     }

     private void setTopControl(Control c) {
         StackLayout layout = (StackLayout) content.getLayout();
         layout.topControl = c;
         if (c instanceof Composite)
             ((Composite) c).layout();
         content.layout();
         container.layout();
     }

     private void updateReturnLinkLabel() {
         String linkText = Messages.StandbyPart_returnToIntro;
         returnLink.setText(linkText);
         AbstractIntroPage page = model.getCurrentPage();
         if (page == null)
             // page will be null in static intro.
 return;

         String toolTip = Messages.StandbyPart_returnTo;
         if (page.getTitle() != null)
             toolTip += " " + page.getTitle(); //$NON-NLS-1$

         returnLink.setToolTipText(toolTip);
     }

     protected void doReturn() {
         // remove the flag to indicate that standbypart is no longer needed.
 ((CustomizableIntroPart) introPart).getControl().setData(
             IIntroConstants.SHOW_STANDBY_PART, null);
         IntroPlugin.setIntroStandby(false);
     }

     /**
      * Calls dispose on all cached IStandbyContentParts.
      *
      */
     public void dispose() {
         Enumeration values = cachedContentParts.elements();
         while (values.hasMoreElements()) {
             ControlKey controlKey = (ControlKey) values.nextElement();
             controlKey.getContentPart().dispose();
         }
         toolkit.dispose();
     }

     /**
      * Save the current state of the standby part. It stores the cached content
      * part id for later creating it on restart. It also creates another
      * subclass momento to also give the standby content part its own name
      * space. This was momentos saved by different content parts will not
      * conflict.
      *
      * @param memento
      * the memento in which to store state information
      */
     public void saveState(IMemento memento) {
         // save cached content part id.
 if (cachedControlKey != null) {
             String contentPartId = cachedControlKey.getContentPartId();
             if (contentPartId == EMPTY_STANDBY_CONTENT_PART)
                 // do not create memento for empty standby.
 return;
             memento.putString(MEMENTO_STANDBY_CONTENT_PART_ID_ATT,
                 contentPartId);
             // give standby part its own child to create a name space for
 // IStandbyPartContent contribution momentos.
 IMemento standbyContentPartMemento = memento
                 .createChild(MEMENTO_STANDBY_CONTENT_PART_TAG);
             // pass new memento to correct standby part.
 IStandbyContentPart standbyContentpart = cachedControlKey
                 .getContentPart();
             if (standbyContentpart != null)
                 standbyContentpart.saveState(standbyContentPartMemento);
         }
     }


     /*
      * Set focus on the IStandbyContentPart that corresponds to the top control
      * in the stack.
      *
      * @see org.eclipse.ui.internal.intro.impl.parts.IStandbyContentPart#setFocus()
      */
     public void setFocus() {
         // grab foxus first, then delegate. This way if content part does
 // nothing on focus, part still works.
 returnLink.setFocus();
         if (cachedControlKey != null)
             cachedControlKey.getContentPart().setFocus();
     }



     /**
      * Checks the standby cache stack if we have already created a similar
      * IStandbyContentPart. If not, returns null.
      *
      * @param standbyContent
      * @return
      */
     private ControlKey getCachedContent(String key) {
         if (cachedContentParts.containsKey(key))
             return (ControlKey) cachedContentParts.get(key);
         return null;
     }

     /*
      * Model class to wrap Control and IStandbyContentPart pairs, along with the
      * representing ID..
      */
     class ControlKey {

         Control c;
         IStandbyContentPart part;
         String contentPartId;

         ControlKey(Control c, IStandbyContentPart part, String contentPartId) {
             this.c = c;
             this.part = part;
             this.contentPartId = contentPartId;
         }

         /**
          * @return Returns the c.
          */
         public Control getControl() {
             return c;
         }

         /**
          * @return Returns the content part.
          */
         public IStandbyContentPart getContentPart() {
             return part;
         }

         /**
          * @return Returns the part id.
          */
         public String getContentPartId() {
             return contentPartId;
         }
     }


 }

